Skip to content

tunnel - reload config#792

Open
commonism wants to merge 3 commits intoronf:developfrom
commonism:tunnel-reload-config
Open

tunnel - reload config#792
commonism wants to merge 3 commits intoronf:developfrom
commonism:tunnel-reload-config

Conversation

@commonism
Copy link

This change fixes an issue when having using alias for tunnel hosts.
The ssh configuration has to be reloaded for the tunnel (alias) to evaluate to the proper values (e.g. User)

I think the unit test needs some additional work, I was unable to assert on something due to lack of values.
It'd be great if it was possible to test the server for logins for this.

e.g.

assert sorted(server.logins) == sorted(["jumper","ckey"])

@ronf
Copy link
Owner

ronf commented Feb 7, 2026

Thanks for the report.

The "reload" keyword wasn't really meant to be used in a call to connect(). AsyncSSH should already be rereading the configuration file for each new connect() call, with updated host/port/username information each time. Reload is meant to be used when you have an already populated SSHClientConnectionOptions object and you need re-evaluate the config after updating one of the matchable inputs, like when doing hostname canonicalization.

Can you provide an example where it fails without this change? I tried connecting through multiple jump hosts where I changed the host, port, and username and it got the right values on each hop, even without this.

@commonism
Copy link
Author

You are correct.
I've updated the unit test using a dedicated jump host service - it works.
I'll keep this open, trying to reproduce.

Thanks

Port is not


INFO:asyncssh:Opening SSH connection to 127.0.0.3, port 22
@commonism
Copy link
Author

Now it reproduces.

Host jump
        HostName 127.0.0.3
        Port {jump_port}
        User jumper

Host target
        Hostname 127.0.0.2
        Port {self._server_port}

Match final host 127.0.0.2
        ProxyJump jump
   ConnectionRefusedError: [Errno 111] Connect call failed ('127.0.0.3', 22)

And my fix does not.

Anyway - is there a way to run a single unit tests?
I struggle due to the relative imports in tests/ without a package ("ImportError: attempted relative import with no known parent package")

@ronf
Copy link
Owner

ronf commented Feb 8, 2026

With your latest commit, you are setting reload=False, which should be the same thing as the original code, since False is the default. So, the only code left in the commit is basically the unit test.

Regarding running a single unit test, you can specify something like:

python3 -m unittest tests.test_forward._TestTCPForwarding.test_proxy_jump_user

Do this from the parent directory (with "asyncssh" and "tests" subdirectories). This should avoid the relative import complaints.

The error you got suggests that it didn't fill in the port number from the config file for forwarding host "jump", or maybe that it did fill it in both overrode it later back to the default, and this failed because there was no listener on the default port 22 on 127.0.0.3.

I think I may be on to something. When I changed my SSH config from:

Host jump
    Hostname localhost
    Port 2222

Match final host ultra
    ProxyJump jump

to:

Match final Host jump
    Hostname localhost
    Port 2222

Match final host ultra
    ProxyJump jump

...it properly set the port number on my jump host to 2222. However, when I left out the "Match final" on the jump host but set it on the block that sets ProxyJump, the port number for the jump host defaulted to 22. Somehow the port number of the "Host jump" rule isn't matching when the ProxyJump is set in a "Match final". This is kind of odd, as the Hostname is still getting evaluated in this case. Otherwise, it'd error out on the nonexistent host named "jump".

I'll continue to dig into this and let you know what I find.

@ronf
Copy link
Owner

ronf commented Feb 8, 2026

Ok - I think I see the problem. When there's a "match final" block present in the config, the config is reloaded after canonicalizing the host:

    host = canon_host if canon_host else options.host
    canonical = bool(canon_host)
    final = options.config.has_match_final()

    if canonical or final:
        options.update(host=host, reload=True, canonical=canonical, final=final)

When this happens, though, the hostname has changed from 'jump' to 'localhost' in my test, causing it to no longer match on the port number set in the "Host jump" block, and replacing it with the default port.

One potential fix here would be to carry forward the port from the previous evaluation (ONLY in this reload case). If the port is overridden again because of the new hostname, that could still happen, but if the port is set on the host alias for the jump host and it sets both Hostname and Port, both of those could carry over to the reload as initial values.

Unfortunately, it's more complicated than this. If you try to do a "Match final host jump" instead of just "Host jump", the first evaluation sets the port to 22 since the match block doesn't match. Later, when reloaded with final=True, the block match succeeded, but by then the port number is set to 22, not () and this takes precedence over the "Port" config directive for the jump host.

I'll need to think about this a bit more.

@ronf
Copy link
Owner

ronf commented Feb 8, 2026

As a workaround, I think you could move the "Port" and "User" directives from "Host jump" to "Host 127.0.0.3", as long as 127.0.0.3 is only ever used as a jump host. If you want to use multiple port numbers on that host, it would get a little more complicated, but you might even be able to do that with if you use "Match" on both the host and port.

I'm curious if setting Hostname and Port for a jump host works in the OpenSSH case even with "Match final" present in the config. Ideally, I'd want AsyncSSH to end up with the same behavior (unless OpenSSH also has a bug in this case).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants